#include "Image/ImageRGBA8.h"

#include "Core/Kernel.h"
#include "Core/Service.h"

#include "GfxDriver/GfxDriver.h"
#include "GfxDriver/Texture.h"

#include <freeimage.h>

namespace Orca {

	//--------------------------------------------------------------------------
	const Str ImageRGBA8::TYPE_NAME = "RGBA8";

	//--------------------------------------------------------------------------
	ImageRGBA8::ImageRGBA8(uint32 w, uint32 h) 
		: Image(w, h) {
	
		mPixels = new uint8[w * h * 4];
	}

	//--------------------------------------------------------------------------
	ImageRGBA8::~ImageRGBA8() {

		delete[] mPixels;
	}

	//--------------------------------------------------------------------------
	ImageRGBA8* ImageRGBA8::create(FIBITMAP *dib) {

		if(!dib) return NULL;

		uint32 w = (uint32)FreeImage_GetWidth(dib);
		uint32 h = (uint32)FreeImage_GetHeight(dib);

		ImageRGBA8 *img = new ImageRGBA8(w, h);

		BYTE *pixels		= (BYTE*)FreeImage_GetBits(dib);
		unsigned int bpp	= FreeImage_GetBPP(dib);
		BYTE *outBuf		= img->mPixels;
		BYTE *inBuf			= (BYTE*)FreeImage_GetBits(dib);
		uint32 inPitch		= (uint32)FreeImage_GetPitch(dib);
		uint32 outPitch		= img->mWidth * 4;

		if(24 == bpp) {

			// 24 bits
			for(uint32 y = 0; y < h; ++y) {

				BYTE *outPixel = (BYTE*)outBuf;
				BYTE *inPixel = (BYTE*)inBuf;

				for(uint32 x = 0; x < w; ++x) {
					outPixel[0]	= inPixel[FI_RGBA_RED];
					outPixel[1]	= inPixel[FI_RGBA_GREEN];
					outPixel[2]	= inPixel[FI_RGBA_BLUE];
					outPixel[3]	= 255;

					inPixel += 3;
					outPixel += 4;
				}
				// next line
				inBuf += inPitch;
				outBuf += outPitch;
			}
		}
		else {

			// 32 bits
			for(uint32 y = 0; y < h; ++y) {

				BYTE *outPixel = (BYTE*)outBuf;
				BYTE *inPixel = (BYTE*)inBuf;

				for(uint32 x = 0; x < w; ++x) {
					outPixel[0]	= inPixel[FI_RGBA_RED];
					outPixel[1]	= inPixel[FI_RGBA_GREEN];
					outPixel[2]	= inPixel[FI_RGBA_BLUE];
					outPixel[3]	= inPixel[FI_RGBA_ALPHA];

					inPixel += 4;
					outPixel += 4;
				}
				// next line
				inBuf += inPitch;
				outBuf += outPitch;
			}
		}

		return img;
	}

	//--------------------------------------------------------------------------
	vec4 ImageRGBA8::getPixelAt(uint32 x, uint32 y) {

		uint8 *ptr = mPixels +  (y * (mWidth << 2) + x);

		float scale = 1.0f / 255.0f;

		return vec4(
			 ptr[0] * scale
			,ptr[1] * scale
			,ptr[2] * scale
			,ptr[3] * scale);
	}

	//--------------------------------------------------------------------------
	void ImageRGBA8::setPixelAt(uint32 x, uint32 y, const vec4 &rgba) {

		uint8 *ptr = mPixels +  (y * (mWidth << 2) + x);

		ptr[0] = (uint8)(rgba[0] * 255.0f);
		ptr[1] = (uint8)(rgba[1] * 255.0f);
		ptr[2] = (uint8)(rgba[2] * 255.0f);
		ptr[3] = (uint8)(rgba[3] * 255.0f);
	}

	//--------------------------------------------------------------------------
	void* ImageRGBA8::getDataPtr() {

		return mPixels;
	}

	//--------------------------------------------------------------------------
	uint32 ImageRGBA8::getPixelSizeInBytes() {

		return 4;
	}

	//--------------------------------------------------------------------------
	bool ImageRGBA8::saveToFile(const Str &filename) {

		FIBITMAP *dib = FreeImage_Allocate(
			 (int)mWidth
			,(int)mHeight
			,32
			,FI_RGBA_RED_MASK
			,FI_RGBA_GREEN_MASK
			,FI_RGBA_BLUE_MASK);

		if(!dib)
			return false;

		uint32 pitch = (uint32)FreeImage_GetPitch(dib);

		BYTE *outBuf = (BYTE*)FreeImage_GetBits(dib);
		BYTE *inBuf = mPixels;

		for(uint32 y = 0; y < mHeight; ++y) {

			BYTE *outPixel = (BYTE*)outBuf;

			for(uint32 x = 0; x < mWidth; ++x) {
				outPixel[FI_RGBA_RED]	= *inBuf++;
				outPixel[FI_RGBA_GREEN]	= *inBuf++;
				outPixel[FI_RGBA_BLUE]	= *inBuf++;
				outPixel[FI_RGBA_ALPHA]	= *inBuf++;
				outPixel += 4;
			}
			// next line
			outBuf += pitch;
		}

		FreeImage_Save(FIF_TARGA, dib, filename.c_str(), TARGA_DEFAULT);

		FreeImage_Unload(dib);

		return true;
	}

	//--------------------------------------------------------------------------
	Texture2d* ImageRGBA8::create2DTexture(bool autoGenMipmap)
	{
		Texture2d *tex = INST<GfxDriver>()->create2DTexture(
			mWidth,
			mHeight,
			PF_INT8_RGBA);

		if(tex) {

			uint8 *dataPtr = (uint8*)tex->requestUpload();

			uint8 *outPtr = dataPtr;
			uint8 *inPtr = mPixels;

			for(uint32 y = 0; y < mHeight; ++y) {
				for(uint32 x = 0; x < mWidth; ++x) {

					outPtr[0] = inPtr[0];
					outPtr[1] = inPtr[1];
					outPtr[2] = inPtr[2];
					outPtr[3] = inPtr[3];

					outPtr += 4;
					inPtr += 4;
				}
			}

			tex->uploadToGPU(autoGenMipmap);
		}

		return tex;
	}

	//--------------------------------------------------------------------------
}